Системное программирование

Процессы и межпроцессное взаимодействие в Linux

Системное программирование

Процессы и межпроцессное взаимодействие в Linux

Эта лекция посвящена процессам в Linux и механизмам межпроцессного взаимодействия (IPC). Понимание процессов и способов их взаимодействия является ключевым для разработки эффективных и надежных системных приложений в Linux.

Процессы и межпроцессное взаимодействие в Linux
Системное программирование

План лекции:

  1. Что такое процесс?
  2. Жизненный цикл процесса
  3. Создание процессов: fork(), exec(), wait()
  4. Пространство процесса и контекст
  5. Потоки vs процессы
  6. Что такое межпроцессное взаимодействие (IPC)?
  7. Типы межпроцессного взаимодействия
  8. Неименованные каналы (pipes)
  9. Именованные каналы (FIFO)
  10. Сигналы (signals)
  11. Системные V IPC: очереди сообщений, сегменты разделяемой памяти, семафоры
  12. Сокеты (sockets) для межпроцессного взаимодействия
  13. Сравнение различных механизмов IPC
  14. Практические примеры
  15. Заключение
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

1. Что такое процесс?

Процесс - это экземпляр выполняющейся программы. Каждый процесс имеет:

  • Собственное виртуальное адресное пространство
  • Собственный стек и кучу
  • Собственные открытые файловые дескрипторы
  • Собственные сигналы и обработчики
  • Идентификатор процесса (PID)
  • Родительский процесс (PPID)

В Linux каждый процесс представлен структурой task_struct в ядре, которая содержит всю информацию о процессе.

Процессы и межпроцессное взаимодействие в Linux
Системное программирование

2. Жизненный цикл процесса

Процесс проходит через несколько состояний:

  • Создание (Created/Ready) - процесс создан и готов к выполнению
  • Выполнение (Running) - процесс выполняется на процессоре
  • Ожидание (Waiting/Blocked) - процесс ожидает события (ввода/вывода, сигнала)
  • Завершение (Terminated/Zombie) - процесс завершился, но еще не полностью удален

Переход между состояниями управляется ядром операционной системы.

Процессы и межпроцессное взаимодействие в Linux
Системное программирование

3. Создание процессов: fork(), exec(), wait()

fork()

Создает точную копию текущего процесса. Родительский и дочерний процессы продолжают выполнение с той же точки кода.

pid_t pid = fork();
if (pid == 0) {
    // Дочерний процесс
} else if (pid > 0) {
    // Родительский процесс
} else {
    // Ошибка
}
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

exec() family

Семейство функций exec() заменяет текущий процесс новой программой. В отличие от fork(), который создает новый процесс, exec() перезаписывает текущий процесс новым образом программы.

Основные варианты exec():

Функция Описание Параметры
execl() Список аргументов path, arg0, arg1, ..., NULL
execv() Массив аргументов path, argv[]
execle() Список + окружение path, arg0, ..., NULL, envp[]
execve() Массив + окружение path, argv[], envp[]
execlp() Список + PATH file, arg0, ..., NULL
execvp() Массив + PATH file, argv[]
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

Правила именования:

  • l - аргументы передаются как список (list)
  • v - аргументы передаются как массив (vector)
  • e - явно передается окружение (environment)
  • p - используется переменная PATH для поиска исполняемого файла
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

Примеры использования exec():

// execl() - список аргументов
execl("/bin/ls", "ls", "-l", "/home", NULL);

// execv() - массив аргументов
char *argv[] = {"ls", "-l", "/home", NULL};
execv("/bin/ls", argv);

// execlp() - поиск в PATH
execlp("ls", "ls", "-l", NULL);

// execle() - с собственным окружением
char *envp[] = {"PATH=/bin:/usr/bin", "USER=student", NULL};
execle("/bin/ls", "ls", "-l", NULL, envp);
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

Особенности работы exec():

  1. Сохраняются следующие атрибуты процесса:

    • PID и PPID
    • Идентификаторы пользователя и группы (UID, GID)
    • Рабочий каталог
    • Маска режима создания файлов (umask)
    • Открытые файловые дескрипторы (если не установлен флаг FD_CLOEXEC)
    • Игнорируемые сигналы
  2. Заменяются следующие атрибуты:

    • Текстовый сегмент (код программы)
    • Данные и стек
    • Обработчики сигналов
    • Адресное пространство процесса
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

Комбинация fork() и exec():

Классический паттерн создания нового процесса с другой программой:

pid_t pid = fork();

if (pid == 0) {  // Дочерний процесс
    // Заменяем себя новой программой
    execl("/bin/ls", "ls", "-l", NULL);
    
    // Этот код выполнится только при ошибке exec
    perror("execl");
    exit(EXIT_FAILURE);
} else if (pid > 0) {  // Родительский процесс
    wait(NULL);  // Ждем завершения дочернего процесса
    printf("Child process finished\n");
} else {  // Ошибка fork
    perror("fork");
    exit(EXIT_FAILURE);
}
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

Обработка ошибок exec():

Функции exec() возвращают -1 только при ошибке. При успешном выполнении управление не возвращается в вызывающий код.

if (execl("/bin/ls", "ls", "-l", NULL) == -1) {
    perror("execl failed");
    exit(EXIT_FAILURE);  // Важно завершить процесс при ошибке
}
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

Поиск программ с использованием PATH:

Функции с суффиксом p (execlp, execvp) ищут исполняемый файл в каталогах, указанных в переменной окружения PATH:

// Ищет 'ls' в каталогах PATH
execlp("ls", "ls", "-l", NULL);

// Эквивалентно:
// /bin/ls, /usr/bin/ls, /usr/local/bin/ls и т.д.
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

Передача окружения:

Функции с суффиксом e позволяют явно задать окружение для нового процесса:

// Создаем новое окружение
char *new_env[] = {
    "PATH=/usr/local/bin:/usr/bin:/bin",
    "HOME=/home/student",
    "USER=student",
    "TERM=xterm-256color",
    NULL
};

// Запускаем программу с новым окружением
execle("/usr/bin/python3", "python3", "script.py", NULL, new_env);
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

Практический пример: shell-команда

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>

void execute_command(char *command) {
    char *argv[100];
    int i = 0;
    
    // Разбиваем команду на аргументы
    char *token = strtok(command, " ");
    while (token != NULL && i < 99) {
        argv[i++] = token;
        token = strtok(NULL, " ");
    }
    argv[i] = NULL;
    
    pid_t pid = fork();
    
    if (pid == 0) {  // Дочерний процесс
        execvp(argv[0], argv);
        perror("execvp");  // Выполнится только при ошибке
        exit(EXIT_FAILURE);
    } else if (pid > 0) {  // Родительский процесс
        wait(NULL);
    } else {
        perror("fork");
    }
}

int main() {
    execute_command("ls -la /home");
    execute_command("ps aux");
    execute_command("date");
    return 0;
}
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

wait() и waitpid()

Родительский процесс ожидает завершения дочернего процесса и получает его статус.

int status;
wait(&status);  // или waitpid(pid, &status, 0)
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

4. Пространство процесса и контекст

Каждый процесс имеет:

  • Текстовый сегмент - исполняемый код
  • Данные - глобальные и статические переменные
  • Стек - локальные переменные, параметры функций
  • Куча - динамически выделяемая память
  • Описатели файлов - открытые файлы
  • Сигналы - обработчики сигналов
  • Атрибуты - PID, PPID, UID, GID, приоритет и др.
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

5. Потоки vs процессы

Процессы:

  • Имеют собственное адресное пространство
  • Изолированы друг от друга
  • Создание требует больше ресурсов
  • Обмен данными сложнее

Потоки (threads):

  • Разделяют адресное пространство процесса
  • Меньшая изоляция
  • Создание требует меньше ресурсов
  • Обмен данными проще
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

6. Что такое межпроцессное взаимодействие (IPC)?

Межпроцессное взаимодействие (IPC - Inter-Process Communication) - это механизмы, позволяющие процессам обмениваться данными и синхронизировать свои действия.

Необходимость IPC возникает когда:

  • Несколько процессов работают над общей задачей
  • Необходимо обмениваться данными между процессами
  • Требуется синхронизация действий процессов
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

7. Типы межпроцессного взаимодействия

Основные механизмы IPC в Linux:

  1. Неименованные каналы (pipes)
  2. Именованные каналы (FIFO)
  3. Сигналы (signals)
  4. Очереди сообщений (message queues)
  5. Разделяемая память (shared memory)
  6. Семафоры (semaphores)
  7. Сокеты (sockets)
  8. Файлы и блокировки
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

8. Неименованные каналы (pipes)

Канал - это однонаправленный канал связи между родственными процессами.

int pipe(int pipefd[2]);
  • pipefd[0] - дескриптор для чтения
  • pipefd[1] - дескриптор для записи
  • Данные читаются в порядке FIFO
  • Канал существует только внутри ядра
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

Пример использования pipes:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
    int pipefd[2];
    pid_t pid;
    char buffer[100];

    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    pid = fork();
    if (pid == 0) {  // Дочерний процесс
        close(pipefd[0]);  // Закрываем чтение
        write(pipefd[1], "Hello from child!", 18);
        close(pipefd[1]);
        exit(EXIT_SUCCESS);
    } else {  // Родительский процесс
        close(pipefd[1]);  // Закрываем запись
        read(pipefd[0], buffer, sizeof(buffer));
        printf("Parent received: %s\n", buffer);
        close(pipefd[0]);
        wait(NULL);
    }

    return 0;
}
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

9. Именованные каналы (FIFO)

FIFO (First In, First Out) - это именованный канал, который существует в файловой системе.

int mkfifo(const char *pathname, mode_t mode);
  • Может использоваться независимыми процессами
  • Существует как специальный файл в файловой системе
  • Обычно блокирует открытие на чтение или запись
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

Пример использования FIFO:

Процесс-писатель:

int fd = open("/tmp/myfifo", O_WRONLY);
write(fd, "Hello FIFO!", 12);
close(fd);

Процесс-читатель:

int fd = open("/tmp/myfifo", O_RDONLY);
read(fd, buffer, sizeof(buffer));
printf("Received: %s\n", buffer);
close(fd);
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

10. Сигналы (signals)

Сигналы - это механизм асинхронного уведомления процессов.

Основные сигналы:

  • SIGINT - прерывание (Ctrl+C)
  • SIGTERM - запрос на завершение
  • SIGKILL - безусловное завершение
  • SIGSEGV - ошибка сегментации
  • SIGCHLD - дочерний процесс завершился
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

Обработка сигналов:

#include <signal.h>
#include <stdio.h>

void signal_handler(int signum) {
    printf("Received signal %d\n", signum);
}

int main() {
    signal(SIGINT, signal_handler);
    // или более надежный способ:
    struct sigaction sa;
    sa.sa_handler = signal_handler;
    sigemptyset(&sa.sa_mask);
    sa.sa_flags = 0;
    sigaction(SIGINT, &sa, NULL);
    
    while(1) {
        sleep(1);
    }
    return 0;
}
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

11. Системные V IPC

Система V IPC включает три механизма:

1. Очереди сообщений (Message Queues)

Позволяют процессам обмениваться сообщениями определенного формата.

int msgget(key_t key, int msgflg);
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
int msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

2. Разделяемая память (Shared Memory)

Позволяет процессам разделять участок памяти.

int shmget(key_t key, size_t size, int shmflg);
void *shmat(int shmid, const void *shmaddr, int shmflg);
int shmdt(const void *shmaddr);
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

3. Семафоры (Semaphores)

Используются для синхронизации процессов.

int semget(key_t key, int nsems, int semflg);
int semop(int semid, struct sembuf *sops, size_t nsops);
int semctl(int semid, int semnum, int cmd, ...);
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

12. Сокеты (sockets) для межпроцессного взаимодействия

Сокеты могут использоваться как для сетевого взаимодействия, так и для IPC между процессами на одной машине.

Доменные сокеты (Unix Domain Sockets):

  • Более быстрые, чем сетевые сокеты
  • Используют файловую систему для адресации
  • Поддерживают как потоковый (SOCK_STREAM), так и дейтаграммный (SOCK_DGRAM) режимы
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

Пример сервера на доменных сокетах:

#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <unistd.h>

int main() {
    int server_fd, client_fd;
    struct sockaddr_un server_addr;
    
    server_fd = socket(AF_UNIX, SOCK_STREAM, 0);
    
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, "/tmp/my_socket");
    
    bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
    listen(server_fd, 5);
    
    client_fd = accept(server_fd, NULL, NULL);
    // Читаем/пишем данные через client_fd
    
    close(client_fd);
    close(server_fd);
    unlink("/tmp/my_socket");
    return 0;
}
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

13. Сравнение различных механизмов IPC

Механизм Скорость Сложность Поддержка связи Ограничения
Pipes Средняя Простая Один к одному Только родственные процессы
FIFO Средняя Простая Один ко многим Блокирующий I/O
Сигналы Высокая Простая Один к одному Только уведомления
Message Queues Средняя Средняя Один ко многим Размер сообщений ограничен
Shared Memory Очень высокая Высокая Многие ко многим Требует синхронизации
Семафоры Высокая Высокая Синхронизация Только синхронизация
Сокеты Средняя Средняя Многие ко многим Больше накладных расходов
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

14. Практические примеры

Пример 1: Родительский процесс, создающий дочерние процессы для параллельной обработки

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main() {
    pid_t pid;
    int num_processes = 3;
    
    for (int i = 0; i < num_processes; i++) {
        pid = fork();
        
        if (pid == 0) {  // Дочерний процесс
            printf("Child %d (PID: %d) is working\n", i, getpid());
            sleep(2);  // Имитация работы
            printf("Child %d finished\n", i);
            exit(0);
        }
    }
    
    // Родительский процесс ждет завершения всех дочерних
    for (int i = 0; i < num_processes; i++) {
        wait(NULL);
    }
    
    printf("All children finished. Parent exiting.\n");
    return 0;
}
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

Пример 2: Использование shared memory для обмена данными

#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <string.h>
#include <unistd.h>

int main() {
    key_t key = ftok("shmfile", 65);
    int shmid = shmget(key, 1024, 0666|IPC_CREAT);
    char *str = (char*) shmat(shmid, (void*)0, 0);
    
    if (fork() == 0) {  // Дочерний процесс
        strcpy(str, "Hello from child!");
        shmdt(str);
    } else {  // Родительский процесс
        wait(NULL);
        printf("Parent read: %s\n", str);
        shmdt(str);
        shmctl(shmid, IPC_RMID, NULL);
    }
    
    return 0;
}
Процессы и межпроцессное взаимодействие в Linux
Системное программирование

15. Заключение

Процессы и межпроцессное взаимодействие являются фундаментальными концепциями в системном программировании Linux. Понимание этих механизмов позволяет создавать эффективные, масштабируемые и надежные приложения.

Ключевые принципы:

  • Выбирайте подходящий механизм IPC в зависимости от требований
  • Учитывайте производительность и сложность реализации
  • Всегда обрабатывайте ошибки и освобождайте ресурсы
  • Используйте синхронизацию при работе с разделяемыми ресурсами

Механизмы IPC являются основой для построения сложных многопроцессных приложений и систем в Linux.

Процессы и межпроцессное взаимодействие в Linux